package p.ithorns.framework.common.utils;

import com.google.common.primitives.Ints;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;


/**
 * @author 2020/2/23-14:01 by Ht.L
 */
public final class ByteUtil {

    private final static Logger log = LoggerFactory.getLogger(ByteUtil.class);

    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];

    private final static int BYTE_SIZE = 2;

    /**
     * 设置byte的高4位和低4位
     *
     * @param h -高4位值
     * @param l -低4位值
     * @return byte
     */
    public static byte setBit(byte h, byte l) {
        //设高四位：
        byte b = 0x00;
        b |= (h << 4);

        //设低四位
        b &= 0xf0;
        b |= (l & 0x0f);
        //(byte) (b | (b << pos));
        return b;
    }

    /**
     * 获取高四位
     *
     * @param data -
     * @return int
     */
    public static int getH4(byte data) {
        return (data & 0xf0) >> 4;
    }

    /**
     * 获取低四位
     *
     * @param data -
     * @return int
     */
    public static int getL4(byte data) {
        return (data & 0x0f);
    }

    /**
     * int 反转
     *
     * @param src
     * @return
     */
    public static int reverse(int src) {
        return ((src & 0x0000FF00) >> 8) | ((src & 0x000000FF) << 8);
    }

    private byte[] bytesToBE(byte[] bytes) {
        int result = 0;
        if (bytes == null) {
            return new byte[0];
        }

        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        buffer.order(ByteOrder.BIG_ENDIAN);
        result = buffer.getInt();

        // 端口号：0 ~ 65535; Short: -32768 ~ 32767
//        short tmp = buffer.getShort();
//        result = tmp < 0 ? getUnsignedShort(tmp) : tmp;

        if (result < 0) {
            log.debug("Length = " + result + " ; original data:" + bytes);
        }
        return buffer.array();
    }

    public static byte[] long2Bytes(long i) {
        byte[] result = new byte[8];
        result[0] = (byte) ((i >>> 56) & 0xFF);
        result[1] = (byte) ((i >>> 48) & 0xFF);
        result[2] = (byte) ((i >>> 40) & 0xFF);
        result[3] = (byte) ((i >>> 32) & 0xFF);
        result[4] = (byte) ((i >>> 24) & 0xFF);
        result[5] = (byte) ((i >>> 16) & 0xFF);
        result[6] = (byte) ((i >>> 8) & 0xFF);
        result[7] = (byte) (i & 0xFF);
        return result;
    }


    /**
     * 合并long数据指定byte数组的指定位置
     *
     * @param i      - int数据
     * @param data   -byte[] 指定数组
     * @param offset -当前偏移量
     */
    public static void int2Bytes(long i, byte[] data, int offset) {
        data[offset] = (byte) ((i >>> 56) & 0xFF);
        data[offset + 1] = (byte) ((i >>> 48) & 0xFF);
        data[offset + 2] = (byte) ((i >>> 40) & 0xFF);
        data[offset + 3] = (byte) ((i >>> 32) & 0xFF);
        data[offset + 4] = (byte) ((i >>> 24) & 0xFF);
        data[offset + 5] = (byte) ((i >>> 16) & 0xFF);
        data[offset + 6] = (byte) ((i >>> 8) & 0xFF);
        data[offset + 7] = (byte) (i & 0xFF);
    }

    public static byte[] int2Bytes(int value, int length) {
        byte[] buffer = null;
        switch (length) {
            case 1:
                if (value > 0xff) {
                    value = 0xff;
                }
                return new byte[]{((byte) value)};

            case 2: {
                if (value > 0xffff) {
                    value = 0xffff;
                }
                buffer = new byte[2];
                int num2 = value % 0x100;
                int num3 = (value - num2) / 0x100;
                buffer[0] = (byte) num3;
                buffer[1] = (byte) num2;
                return buffer;
            }

            case 3: {
                buffer = new byte[3];
                int num4 = value / 0x10000;
                int num5 = (value % 0x10000) / 0x100;
                int num6 = (value % 0x10000) % 0x100;
                buffer[0] = (byte) num4;
                buffer[1] = (byte) num5;
                buffer[2] = (byte) num6;
                return buffer;
            }

            default:
                log.warn("Some errors occurred during the to convert Int to Bytes.");
                return null;
        }

    }

    public static long bytes2Long(byte[] bs) {
        return bytes2Long(bs, 0);
    }

    public static long bytes2Long(byte[] bs, int offset) {
        long value = 0;
        for (int i = 0; i < 8; i++) {
            int shift = (8 - 1 - i) << 3;
            value += (long) (bs[i + offset] & 0xFF) << shift;
        }
        return value;
    }

    public static long bytes2LongLE(byte[] bs) {
        long value = 0;
        for (int i = 0; i < 8; i++) {
            value += (long) (bs[i] & 0xff) << (i << 3);
        }
        return value;
    }

    public static byte[] int2Bytes(int i) {
        byte[] result = new byte[4];
        result[0] = (byte) ((i >>> 24) & 0xFF);
        result[1] = (byte) ((i >>> 16) & 0xFF);
        result[2] = (byte) ((i >>> 8) & 0xFF);
        result[3] = (byte) (i & 0xFF);
        return result;
    }

    /**
     * 合并int数据指定byte数组的指定位置
     *
     * @param i      - int数据
     * @param data   -byte[] 指定数组
     * @param offset -当前偏移量
     */
    public static void int2Bytes(int i, byte[] data, int offset) {
        data[offset] = (byte) ((i >>> 24) & 0xFF);
        data[offset + 1] = (byte) ((i >>> 16) & 0xFF);
        data[offset + 2] = (byte) ((i >>> 8) & 0xFF);
        data[offset + 3] = (byte) (i & 0xFF);
    }

    public static int bytes2Int(byte[] bs) {
        return bytes2Int(bs, 0);
    }

    /**
     * int 转 byte[]
     * 小端序
     *
     * @param i- int
     * @return byte[]
     */
    private byte[] int2BytesLE(int i) {
        byte[] bytes = new byte[4];
        bytes[3] = (byte) ((i) >> 24);
        bytes[2] = (byte) ((i << 8) >> 24);
        bytes[1] = (byte) ((i << 16) >> 24);
        bytes[0] = (byte) ((i << 24) >> 24);
        return bytes;
    }

    /**
     * 转换byte数组为int（小端）
     *
     * @return
     * @note 数组长度至少为4，按小端方式转换,即传入的bytes是小端的，按这个规律组织成int
     */
    public static int bytes2IntLE(byte[] bytes) {
        if (bytes.length < 4) {
            return -1;
        }

        int iRst = (bytes[0] & 0xFF);
        iRst |= (bytes[1] & 0xFF) << 8;
        iRst |= (bytes[2] & 0xFF) << 16;
        iRst |= (bytes[3] & 0xFF) << 24;

        return iRst;
    }

    public static int bytes2Int(byte[] bs, int offset) {
        int value = 0;
        for (int i = 0; i < 4; i++) {
            int shift = (4 - 1 - i) << 3;
            value += (bs[i + offset] & 0xFF) << shift;
        }
        return value;
    }

    public static byte[] reverseByteArray(byte[] originArray) {
        byte temp;
        int length = originArray.length;
        for (int i = 0; i < (length >> 1); i++) {
            temp = originArray[i];
            originArray[i] = originArray[length - i - 1];
            originArray[length - i - 1] = temp;
        }
        return originArray;
    }

    /**
     * short 转 byte[]
     *
     * @param s - short
     * @return byte[]
     */
    public static byte[] short2Bytes(short s) {
        byte[] bs = new byte[2];
        bs[0] = (byte) ((s & 0xFF00) >>> 8);
        bs[1] = (byte) (s & 0xFF);
        return bs;
    }

    /**
     * 合并short数据指定byte数组的指定位置
     *
     * @param i
     * @param data
     * @param offset
     */
    public static void short2Bytes(short i, byte[] data, int offset) {
        data[offset] = (byte) ((i & 0xFF00) >>> 8);
        data[offset + 1] = (byte) (i & 0xFF);
    }

    /**
     * short 转 byte[]
     * 高字节在前
     *
     * @param s -short
     * @return byte[]
     */
    public static byte[] short2BytesBE(short s) {
        byte[] bytes = new byte[2];
        bytes[0] = (byte) ((s << 24) >> 24);
        bytes[1] = (byte) ((s << 16) >> 24);
        return bytes;
    }

    /**
     * byte[] 转 short
     *
     * @param bytes -byte[]
     * @return short
     */
    public static short bytes2Short(byte[] bytes) {
        return bytes2Short(bytes, 0);
    }


    public static short bytes2Short(byte[] bs, int offset) {
        short value = 0;
        value = (short) (((bs[offset] < 0 ? bs[offset] + 256 : bs[offset]) << 8)
                + (bs[offset + 1] < 0 ? bs[offset + 1] + 256 : bs[offset + 1]));
        return value;
    }

    /**
     * short 转 byte[]
     * 小端序 LE
     *
     * @param n
     * @return
     */
    public static byte[] short2BytesLE(short n) {
        byte[] bs = new byte[2];
        bs[1] = (byte) (n & 0xff);
        bs[0] = (byte) ((n >> 8) & 0xff);
        return bs;
    }

    public static short bytes2ShortLE(byte[] bs) {
        return (short) (bs[1] & 0xff << 8 | (bs[0] & 0xff));
    }


    /**
     * 将byte转换为一个长度为8的byte数组，数组每个值代表bit
     */
    public static byte[] byte2BinArray(byte b) {
        byte[] array = new byte[8];
        for (int i = 7; i >= 0; i--) {
            array[i] = (byte) (b & 1);
            b = (byte) (b >> 1);
        }
        return array;
    }

    /**
     * 把byte转为字符串的bit
     */
    public static String byte2BitStr(byte bs) {
        return ""
                + (byte) ((bs >> 7) & 0x1) + (byte) ((bs >> 6) & 0x1)
                + (byte) ((bs >> 5) & 0x1) + (byte) ((bs >> 4) & 0x1)
                + (byte) ((bs >> 3) & 0x1) + (byte) ((bs >> 2) & 0x1)
                + (byte) ((bs >> 1) & 0x1) + (byte) ((bs >> 0) & 0x1);
    }

    /**
     * 字节转换为浮点
     *
     * @param b     字节（至少4个字节）
     * @param index 开始位置
     * @return float
     */
    public static float bytesToFloat(byte[] b, int index) {
        int l = b[index];
        l &= 0xff;
        l |= ((long) b[index + 1] << 8);
        l &= 0xffff;
        l |= ((long) b[index + 2] << 16);
        l &= 0xffffff;
        l |= ((long) b[index + 3] << 24);
        return Float.intBitsToFloat(l);
    }

    /**
     * 浮点转换为字节
     *
     * @param f -
     * @return byte[]
     */
    public static byte[] floatToBytes(float f) {

        // 把float转换为byte[]
        int fbit = Float.floatToIntBits(f);

        byte[] b = new byte[4];
        for (int i = 0; i < 4; i++) {
            b[i] = (byte) (fbit >> (24 - i * 8));
        }

        // 翻转数组
        int len = b.length;
        // 建立一个与源数组元素类型相同的数组
        byte[] dest = new byte[len];
        // 为了防止修改源数组，将源数组拷贝一份副本
        System.arraycopy(b, 0, dest, 0, len);
        byte temp;
        // 将顺位第i个与倒数第i个交换
        for (int i = 0; i < (len >> 1); ++i) {
            temp = dest[i];
            dest[i] = dest[len - i - 1];
            dest[len - i - 1] = temp;
        }

        return dest;

    }

    public static byte[] doubleToBytes(double d) {
        long value = Double.doubleToRawLongBits(d);
        byte[] byteRet = new byte[8];
        for (int i = 0; i < 8; i++) {
            byteRet[i] = (byte) ((value >> 8 * i) & 0xff);
        }
        return byteRet;
    }

    public static double bytesToDouble(byte[] arr) {
        long value = 0;
        for (int i = 0; i < 8; i++) {
            value |= ((long) (arr[i] & 0xff)) << (8 * i);
        }
        return Double.longBitsToDouble(value);
    }

    /** *************************************** Char & String <==> Bytes ***********************************************/

    /**
     * Convert char to byte
     *
     * @param c char
     * @return byte
     */
    private static byte char2Byte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }


    public static byte[] str2Bytes(String str) {
        if (StringUtils.isEmpty(str)) {
            return new byte[0];
        }
        try {
            return str.getBytes("utf-8");
        } catch (UnsupportedEncodingException e) {
            return new byte[0];
        }
    }

    /**
     * 获取字符串
     *
     * @param data
     * @param offset
     * @param byteCount
     * @return
     */
    public static String bytes2Str(byte[] data, int offset, int byteCount) {
        try {
            return new String(data, offset, byteCount, "utf-8");
        } catch (UnsupportedEncodingException e) {
            return "";
        }
    }

    /**
     * @see ByteUtil#bytes2Str(byte[], int, int)
     */
    public static String bytes2Str(byte[] data, int byteCount) {
        return bytes2Str(data, 0, byteCount);
    }

    /**
     * @see ByteUtil#bytes2Str(byte[], int)
     */
    public static String bytes2Str(byte[] data) {
        return bytes2Str(data, data.length);
    }


    /** *************************************** Hex <==> Bytes ***********************************************/

    /**
     * byte 转 Hex字符串
     *
     * @param bs - byte
     * @return String
     */
    public static String byte2HexStr(byte bs) {
        return String.format("%02X", bs);
    }

    /**
     * serialize 转 Hex字符串
     *
     * @param bytes -
     * @return String
     */
    public static String bytes2HexStr(byte[] bytes) {
        if (null == bytes) {
            return String.format("%02X", 0);
        }
        StringBuilder sb = new StringBuilder();
        for (byte aByte : bytes) {
            sb.append(String.format("%02X", aByte));
        }
        return sb.toString();
    }

    /**
     * short 转 Hex字符串
     *
     * @param s -short
     * @return String
     */
    public static String short2HexStr(short s) {
        byte[] bytes = short2BytesLE(s);
        return bytes2HexStr(bytes);
    }

    /**
     * Convert byte[] to hex
     * string.这里我们可以将byte转换成int，然后利用Integer.toHexString(int)来转换成16进制字符串。
     *
     * @param src byte[] data
     * @return hex string
     */
    public static String bytes2HexStr(byte[] src, String split) {
        StringBuffer buffer = new StringBuffer();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                buffer.append(0);
            }
            buffer.append(hv.toUpperCase());
            buffer.append(split);
        }
        if (split.length() > 0) {
            buffer.setLength(buffer.length() - split.length());
        }
        return buffer.toString();
    }


    /**
     * Convert hex string to byte[]
     *
     * @param hexString the hex string
     * @return byte[]
     */
    public static byte[] hexStr2Bytes(String hexString, String split) {
        if ("".equals(hexString)) {
            return null;
        }
        hexString = hexString.concat(split).toUpperCase();
        int length = hexString.length() / (2 + split.length());
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * (2 + split.length());
            d[i] = (byte) (char2Byte(hexChars[pos]) << 4 | char2Byte(hexChars[pos + 1]));
        }
        return d;
    }

    public static byte[] hexStr2Bytes(String hexString) {
        return hexStr2Bytes(hexString, "");
    }


    /**
     * ************************************* Bytes Tools
     ***********************************************/
    public static byte[] file2Bytes(File image) {
        if (image == null) {
            return null;
        }
        InputStream is;
        try {
            is = new FileInputStream(image);
        } catch (FileNotFoundException e) {
            return null;
        }
        byte[] data = new byte[(int) image.length()];
        try {
            is.read(data);
        } catch (IOException e) {
            return null;
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
        }
        return data;
    }

    public static byte[] clone(byte[] array) {
        return array == null ? null : array.clone();
    }

    public static byte[] add(byte[] a, byte[] bs) {
        byte[] arr = new byte[a.length + bs.length];
        copyArray(arr, a);
        copyArray(arr, a.length, bs);
        return arr;
    }

    public static byte[] addAll(byte[] array1, byte... array2) {
        if (array1 == null) {
            return clone(array2);
        } else if (array2 == null) {
            return clone(array1);
        } else {
            byte[] joinedArray = new byte[array1.length + array2.length];
            System.arraycopy(array1, 0, joinedArray, 0, array1.length);
            System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
            return joinedArray;
        }
    }

    public static void copyArray(byte[] dest, byte[] src) {
        copyArray(dest, 0, src);
    }

    public static void copyArray(byte[] dest, int destOffset, byte[] src) {
        copyArray(dest, destOffset, src, 0);
    }

    public static void copyArray(byte[] dest, int destOffset, byte[] src, int srcOffset) {
        copyArray(dest, destOffset, src, srcOffset, src.length - srcOffset);
    }

    public static void copyArray(byte[] dest, int destOffset, byte[] src, int srcOffset, int length) {
        assert null != dest;
        assert null != src;
        System.arraycopy(src, srcOffset, dest, destOffset, length);
    }


    /**
     * 把IP地址转化为int
     *
     * @param ipAddr
     * @return int
     */
    public static byte[] ip2Bytes(String ipAddr) {
        byte[] ret = new byte[4];
        try {
            String[] ipArr = ipAddr.split("\\.");
            ret[0] = (byte) (Integer.parseInt(ipArr[0]) & 0xFF);
            ret[1] = (byte) (Integer.parseInt(ipArr[1]) & 0xFF);
            ret[2] = (byte) (Integer.parseInt(ipArr[2]) & 0xFF);
            ret[3] = (byte) (Integer.parseInt(ipArr[3]) & 0xFF);
            return ret;
        } catch (Exception e) {
            throw new IllegalArgumentException(ipAddr + " is invalid IP");
        }
    }

    /**
     * byte[]转化为IP
     *
     * @param bytes
     * @return int
     */
    public static String bytes2Ip(byte[] bytes) {
        return new StringBuffer().append(bytes[0] & 0xFF).append('.').append(
                        bytes[1] & 0xFF).append('.').append(bytes[2] & 0xFF)
                .append('.').append(bytes[3] & 0xFF).toString();
    }

    /**
     * IP 端口转byte[]
     *
     * @param port
     * @return
     */
    public static byte[] ipPort2Bytes(int port) {
        if (port > 65535) {
            log.warn("IP port larger than 65535");
            // throw new RuntimeException("IP port larger than 65535");
            port = 65535;
        }

        // 直接使用int转byte[] 是4位hex; 使用short, 端口又无法达到65535; 所以使用如下方法
        String hexString = String.format("%04x", port);

        return hexStr2Bytes(hexString);

    }

    public static int bytes2IpPort(byte[] bytes) {
        return bytes2Short(bytes);
    }

    /**
     * 将Mac地址字符串转换为byte数组
     *
     * @param mac Mac地址字符串，格式如：78:44:fd:c9:87:a0
     * @return 该Mac地址的byte数组形式
     */
    public static byte[] mac2Bytes(String mac) {
        if (mac.contains("-")) {
            mac = StringUtil.replaceAll(mac, "-", ":");
        }
        byte[] macBytes = new byte[6];

        String[] strArr = mac.split(":");
        for (int i = 0; i < strArr.length; i++) {
            int value = Integer.parseInt(strArr[i], 16);
            macBytes[i] = (byte) value;
        }

        return macBytes;
    }

    /**
     * 将Mac地址的数组形式转换为字符串形式
     *
     * @param macBytes mac地址的数组形式
     * @return Mac地址的字符串，格式如：78:44:fd:c9:87:a0
     */
    public static String bytes2Mac(byte[] macBytes) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < macBytes.length; i++) {
            builder.append(':').append(byte2HexStr(macBytes[i]));
        }
        return builder.substring(1).toUpperCase();
    }

    //b为传入的字节，i为第几位（范围0-7），如要获取bit0，则i=0
    public int getBit(byte b, int i) {
        return (int) ((b >> i) & 0x1);
    }

    /**
     * 取byte中的值
     * 大端
     * b为传入的字节，start是起始位，length是长度，如要获取bit0-bit4的值，则start为0，length为5
     */
    public static int getBits(byte bs, int start, int length) {
        return (int) ((bs >> start) & (0xFF >> (8 - length)));
    }

    /**
     * 取byte中的值
     * 小端
     * 从第n位(高位)开始取k个位数的值
     *
     * @param bs     -byte
     * @param start  -开始位. 从0开始
     * @param length -取几位长度
     * @return 截取数的值
     */
    public static int getBitsLE(byte bs, int start, int length) {
        return (bs >> (8 - start - length)) & (0xFF >> length);
    }


    public static byte[] str2BcdBytes(String s) {
        // 把形如 0123456789ab 的字符串转换为形如 {0x01, 0x23, 0x45, 0x67, 0x89, 0xab} 的数组
        if (StringUtils.isBlank(s)) {
            return null;
        }

        int len = s.length();
        // s.Length 必须为偶数
        if (len % BYTE_SIZE == 1) {
            return null;
        }

        byte[] data = new byte[len >> 1];
        for (int i = 0; i < len; i += BYTE_SIZE) {
            String tmp = s.substring(i, BYTE_SIZE);
            int num = Ints.tryParse(tmp);
            data[i / 2] = (byte) num;
        }

        return data;
    }

    public static byte[] subarray(byte[] array, int startIndexInclusive, int endIndexExclusive) {
        if (array == null) {
            return null;
        } else {
            if (startIndexInclusive < 0) {
                startIndexInclusive = 0;
            }

            if (endIndexExclusive > array.length) {
                endIndexExclusive = array.length;
            }

            int newSize = endIndexExclusive - startIndexInclusive;
            if (newSize <= 0) {
                return EMPTY_BYTE_ARRAY;
            } else {
                byte[] subarray = new byte[newSize];
                System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
                return subarray;
            }
        }
    }

    public static char[] toChars(byte[] bytes) {
        ByteBuffer bb = ByteBuffer.allocate(bytes.length);
        bb.put(bytes).flip();
        CharBuffer cb = StandardCharsets.UTF_8.decode(bb);
        return cb.array();
    }

    public static byte[] copyBytes(InputStream zis) throws IOException {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            // 这里zis不能写在try里，会被关闭
            ReadableByteChannel readChannel = Channels.newChannel(zis);
            // 分配缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(8196);
            while (readChannel.read(buffer) != -1) {
                buffer.flip(); // 切换为读模式
                out.write(buffer.array());
                buffer.clear(); // 清空缓冲区
            }
            return out.toByteArray();
        }
    }

}